<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <h1>{{title}}</h1>
  </div>
  <script src="./vue3.js"></script>
  <script src="./compiler.js"></script>
  <script>
    // 1.返回vnode函数
    function h(tag, props, children) {
      return { tag, props, children }
    }
    const Vue = {
      // 创建渲染器
      // opts中传入节点相关操作
      createRenderer({ querySelector, insert, createElement }) {
        // createApp实际上由渲染器实现
        return {
          createApp(options) {
            // 返回应用程序实例app
            return {
              // 传入宿主
              mount(selector) {
                // 传入的宿主获取
                const parent = querySelector(selector)

                // 处理vue2 options选项
                if (options.setup) {
                  // 存入app实例上
                  this.setupState = options.setup()
                }
                if (options.data) {
                  this.data = options.data()
                }

                // 设置render上下文
                this.proxy = new Proxy(this, {
                  get(target, key) {
                    // 优先从setupState上获取，其次是data
                    if (key in target.setupState) {
                      return target.setupState[key]
                    } else {
                      return target.data[key]
                    }
                  },
                  set(target, key, val) {
                    if (key in target.setupState) {
                      target.setupState[key] = val
                    } else {
                      target.data[key] = val
                    }
                  }
                })

                // 将options转换为dom并追加到宿主
                // 获取模板，将数据绑上去
                // 模板是有编译函数编译而来
                if (!options.render) {
                  options.render = compile(parent.innerHTML)
                }

                this.update = effect(() => {
                  // vnode
                  // 渲染vdom
                  const vnode = options.render(this.proxy)

                  if (!this.isMounted) {
                    const el = this.createElm(vnode)
                    parent.innerHTML = ''
                    insert(el, parent)
                    this.isMounted = true
                  }
                  else {
                    this.__patch__(this._vnode, vnode)
                  }
                  this._vnode = vnode;
                })
              },
              // ⾸次创建
              createElm(vnode) {
                const el = createElement(vnode.tag)
                if (typeof vnode.children === 'string') {
                  el.textContent = vnode.children
                } else {
                  vnode.children.forEach(child => insert(this.createElm(child), el))
                }
                vnode.el = el
                return el
              },

              __patch__(oldVnode, vnode) {
                if (oldVnode?.nodeType) {
                  // init
                  const parent = oldVnode.parentElement;
                  const refElm = oldVnode.nextSibling;
                  // 递归创建dom结构
                  const el = this.createElm(vnode);
                  parent.insertBefore(el, refElm);
                  parent.removeChild(oldVnode);
                }
                else {
                  // update
                  // 操作谁
                  const el = (vnode.el = oldVnode.el);
                  // diff
                  if (oldVnode.tag === vnode.tag) {
                    // sameVnode
                    // props...
                    // children
                    const oldCh = oldVnode.children;
                    const newCh = vnode.children;

                    if (typeof newCh === "string") {
                      if (typeof oldCh === "string") {
                        // text update
                        if (newCh !== oldCh) {
                          el.textContent = newCh;
                        }
                      } else {
                        // replace elements with text
                        el.textContent = newCh;
                      }
                    } else {
                      if (typeof oldCh !== "string") {
                        // updateChildren
                        this.updateChildren(el, oldCh, newCh);
                      } else {
                        // replace text with elements
                        el.textContent = "";
                        newCh.forEach((ch) => {
                          el.appendChild(this.createElm(ch));
                        });
                      }
                    }
                  } else {
                    // replace
                  }
                }
              },

              // 这里是diff的简版不优化，不考虑key
              updateChildren(parentElm, oldCh, newCh) {
                // 这⾥暂且直接patch对应索引的两个节点
                const len = Math.min(oldCh.length, newCh.length);
                for (let i = 0; i < len; i++) {
                  this.__patch__(oldCh[i], newCh[i]);
                }
                // newCh若是更⻓的那个，说明有新增
                if (newCh.length > oldCh.length) {
                  newCh.slice(len).forEach((child) => {
                    const el = this.createElm(child);
                    parentElm.appendChild(el);
                  });
                } else if (newCh.length < oldCh.length) {
                  // oldCh若是更⻓的那个，说明有删减
                  oldCh.slice(len).forEach((child) => {
                    parentElm.removeChild(child.el);
                  });
                }
              }
            }
          }
        }
      },
      // 传入根组件配置
      createApp(options) {
        // 1.根据当前web平台创建一个renderer
        const renderer = Vue.createRenderer({
          querySelector(sel) {
            return document.querySelector(sel)
          },
          insert(child, parent, anchor) {
            parent.insertBefore(child, anchor || null)
          },
          // 添加⼀个创建元素
          createElement(tag) {
            return document.createElement(tag)
          }
        })
        return renderer.createApp(options)
      }
    }
  </script>
  <script>
    // 1.创建实例
    // vue2: new Vue()
    // vue3: createApp()
    const { createApp } = Vue
    // 传入根组件配置
    const app = createApp({
      data() {
        return {
          title: 'hello,vue3!'
        }
      },
      setup() {
        const state = reactive({
          title: 'vue3,hello!'
        })

        setTimeout(() => {
          state.title = 'hello,vue3!!!!'
        }, 2000)

        return state
      }
    })
    // 更简洁
    app.mount('#app')
  </script>
</body>

</html>